/*
 * MIT License
 *
 * Copyright (c) 2023 北京凯特伟业科技有限公司
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.je.bpm.engine.impl.bpmn.behavior;

import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.google.common.base.Strings;
import com.je.bpm.core.model.BpmnModel;
import com.je.bpm.core.model.FlowElement;
import com.je.bpm.core.model.FlowNode;
import com.je.bpm.core.model.config.task.TaskEarlyWarningAndPostponementConfigImpl;
import com.je.bpm.core.model.config.task.TaskRandomConfigImpl;
import com.je.bpm.core.model.config.task.assignment.TaskAssigneeConfigImpl;
import com.je.bpm.core.model.task.*;
import com.je.bpm.engine.ActivitiException;
import com.je.bpm.engine.delegate.DelegateExecution;
import com.je.bpm.engine.history.HistoricActivityInstance;
import com.je.bpm.engine.impl.context.Context;
import com.je.bpm.engine.impl.delegate.TriggerableActivityBehavior;
import com.je.bpm.engine.impl.identity.Authentication;
import com.je.bpm.engine.impl.interceptor.CommandContext;
import com.je.bpm.engine.impl.persistence.entity.*;
import com.je.bpm.runtime.shared.identity.BO.ParserUserBo;

import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;

import static com.je.bpm.engine.impl.bpmn.behavior.KaiteBaseUserTaskActivityBehavior.DIRECT_TASK_ID;
import static com.je.bpm.engine.impl.bpmn.behavior.KaiteBaseUserTaskActivityBehavior.RANDOM_TASK_ASSIGNEE;

/**
 * Superclass for all 'connectable' BPMN 2.0 process elements: tasks, gateways and events. This means that any subclass can be the source or target of a sequenceflow.
 * Corresponds with the notion of the 'flownode' in BPMN 2.0.
 * 该抽象类适用于所有可连接的BPMN 2流转元素：任务、网关和事件。这意味着任何子类都可以是sequenceflow的源或目标
 */
public abstract class FlowNodeActivityBehavior implements TriggerableActivityBehavior {

    private static final long serialVersionUID = 1L;

    protected BpmnActivityBehavior bpmnActivityBehavior = new BpmnActivityBehavior();

    /**
     * Default behaviour: just leave the activity with no extra functionality.
     */
    @Override
    public void execute(DelegateExecution execution) {
        leave(execution);
    }

    public String getUserIds(BpmnModel bpmnModel, FlowElement flowElement, CommandContext commandContext,
                             TaskAssigneeConfigImpl taskAssigneeConfig, DelegateExecution execution, String taskDefinitionKey) {
        //人员参照
        String userId = "";
        String directTask = execution.getVariable(DIRECT_TASK_ID, String.class);
        TaskAssigneeConfigImpl.ReferToEnum referToEnum = taskAssigneeConfig.getReferTo();
        if (referToEnum != null && referToEnum.toString().equals(TaskAssigneeConfigImpl.ReferToEnum.STARTUSER.toString())) {
            userId = commandContext.getIdentityLinkEntityManager().findStartUserIdentityLinkByProcessInstanceUser(execution.getProcessInstanceId()).getUserId();
        } else {
            userId = Authentication.getAuthenticatedUser().getDeptId();
        }
        String prod = (String) execution.getTransientVariable(CommandContext.PROD);
        Map<String, Object> bean = (Map<String, Object>) commandContext.getAttribute(commandContext.BEAN);
        Map<String, String> assigner = getAssigner(execution.getProcessInstanceId(), taskDefinitionKey);
        String starter = "";
        IdentityLinkEntity identityLinkEntity = commandContext.getIdentityLinkEntityManager().findStartUserIdentityLinkByProcessInstanceUser(execution.getProcessInstanceId());
        if (identityLinkEntity != null) {
            starter = commandContext.getIdentityLinkEntityManager().
                    findStartUserIdentityLinkByProcessInstanceUser(execution.getProcessInstanceId()).getUserId();
        } else {
            if (((ExecutionEntityImpl) execution).getTasks().size() > 0) {
                starter = ((ExecutionEntityImpl) execution).getTasks().get(0).getAssignee();
            }
        }

        Object userObject = commandContext.getProcessEngineConfiguration().getResultUserParser().parserResultUser(
                ParserUserBo.build(true, (KaiteBaseUserTask) flowElement, false, taskAssigneeConfig, userId, directTask,
                        bean, prod, assigner.get("assigner"), assigner.get("frontAssigner"), starter));
        if (userObject instanceof Map) {
            Boolean multi = false;
            //解析人员
            if (flowElement instanceof KaiteMultiUserTask || flowElement instanceof KaiteCandidateUserTask ||
                    flowElement instanceof KaiteCounterSignUserTask || flowElement instanceof KaiteRandomUserTask) {
                multi = true;
            }
            List<String> userIdList = commandContext.getProcessEngineConfiguration().getResultUserParser().
                    parserResultUserToListString(userObject, multi);
            return userIdList.stream().collect(Collectors.joining(","));
        } else {
            throw new ActivitiException("获取人员信息异常!");
        }
    }

    /**
     * 随机节点获取人员信息
     */
    public String getRandomTaskUserIds(BpmnModel bpmnModel, FlowElement flowElement, CommandContext commandContext,
                                       TaskAssigneeConfigImpl taskAssigneeConfig, DelegateExecution execution, TaskEntity task) {
        KaiteRandomUserTask kaiteRandomUserTask = (KaiteRandomUserTask) flowElement;

        //（因为存在取回。退回。驳回的情况）先从变量中获取随机节点人员信息
        Object randomTaskAssignee = execution.getVariable(RANDOM_TASK_ASSIGNEE);
        if (randomTaskAssignee != null) {
            JSONArray taskAssignee = JSONArray.parseArray(randomTaskAssignee.toString());
            for (int i = 0; i < taskAssignee.size(); i++) {
                JSONObject jsonObject = taskAssignee.getJSONObject(i);
                if (!Strings.isNullOrEmpty(jsonObject.getString(execution.getProcessInstanceId() + "-" + kaiteRandomUserTask.getId()))) {
                    return jsonObject.getString(execution.getProcessInstanceId() + "-" + kaiteRandomUserTask.getId());
                }
            }
        }

        String assigners = getUserIds(bpmnModel, flowElement, commandContext, taskAssigneeConfig, execution, task.getTaskDefinitionKey());

        String taskId = task.getId();

        if (assigners.isEmpty()) {
            throw new ActivitiException("获取人员信息异常!");
        }
        //获取流程的key
        String processKey = bpmnModel.getProcesses().get(0).getId();
        String[] assignerArrs = assigners.split(",");
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

        //同一个流程实例，每次流程执行到随机节点，先查找当前实例在表有没有执行人信息。如果没有新增。随机选 选后记录
        // 如果有 根据随机节点的配置，轮询（查出当前流程实例下所有人员信息根据执行次数排序，选最小的，如果多个最小的随机选一个）, 随机
        // 修改执行次数，时间
        TaskRandomConfigImpl taskRandomConfig = kaiteRandomUserTask.getTaskRandomConfig();
        //轮询
        boolean distributedPolling = taskRandomConfig.isDistributedPolling();
        //随机
        boolean distributeRandom = taskRandomConfig.isDistributeRandom();

        List<RandomTaskEntity> randomTaskEntityList = commandContext.getProcessEngineConfiguration().getRandomTaskDataManager().selectRandomByProcessKeyAndFlowElementIdOrderByCount(processKey, kaiteRandomUserTask.getId());
        List<RandomTaskEntity> randomTaskEntityListResult = new ArrayList<>();
        if (randomTaskEntityList != null && randomTaskEntityList.size() > 0) {
            //先看这次运行人员在不在之前运行节点的人员中，如果不在把人员信息记录
            for (int i = 0; i < assignerArrs.length; i++) {
                String id = assignerArrs[i];
                List<RandomTaskEntity> userIdList = randomTaskEntityList.stream().filter((RandomTaskEntity a) -> a.getUserId().equals(id)).collect(Collectors.toList());
                if (userIdList != null && userIdList.size() != 0) {
                    randomTaskEntityListResult.addAll(userIdList);
                    continue;
                }
                RandomTaskEntityImpl randomTaskEntity = new RandomTaskEntityImpl();
                randomTaskEntity.setTaskId(taskId);
                randomTaskEntity.setUserId(id);
                String time = simpleDateFormat.format(new Date());
                randomTaskEntity.setTime(time);
                randomTaskEntity.setCountNum("0");
                randomTaskEntity.setProcessInstanceId(execution.getProcessInstanceId());
                randomTaskEntity.setProcessDefinitionId(execution.getProcessDefinitionId());
                randomTaskEntity.setFlowElementId(kaiteRandomUserTask.getId());
                randomTaskEntity.setProcessKey(processKey);
                commandContext.getProcessEngineConfiguration().getRandomTaskDataManager().insert(randomTaskEntity);
                randomTaskEntityListResult.add(randomTaskEntity);
            }
            RandomTaskEntity randomTaskEntity = null;
            if (distributedPolling) {
                System.out.println("排序前结果：" + randomTaskEntityListResult.toString());
                Collections.sort(randomTaskEntityListResult, (o1, o2) -> o2.getCountNum().compareTo(o1.getCountNum()));
                System.out.println("排序后结果：" + randomTaskEntityListResult.toString());
                String countNum = randomTaskEntityListResult.get(randomTaskEntityListResult.size() - 1).getCountNum();
                //获取执行最少次数相同的的集合，在其中随机选一个
                List<RandomTaskEntity> list = null;
                list = randomTaskEntityListResult.stream().filter((RandomTaskEntity a) -> a.getCountNum().equals(countNum)).collect(Collectors.toList());
                System.out.println("排序后执行数量最小的结果：" + list.toString());
                int a = (int) (Math.random() * list.size());
                randomTaskEntity = list.get(a);
            }
            if (distributeRandom) {
                int a = (int) (Math.random() * randomTaskEntityListResult.size());
                randomTaskEntity = randomTaskEntityListResult.get(a);

            }
            if (!distributedPolling && !distributeRandom) {
                int a = (int) (Math.random() * randomTaskEntityListResult.size());
                randomTaskEntity = randomTaskEntityListResult.get(a);
            }

            if (randomTaskEntity != null) {
                //修改执行人执行次数
                randomTaskEntity.setCountNum(String.valueOf(Long.parseLong(randomTaskEntity.getCountNum()) + 1));
                String time = simpleDateFormat.format(new Date());
                randomTaskEntity.setTime(time);
                commandContext.getProcessEngineConfiguration().getRandomTaskDataManager().update(randomTaskEntity);
                //随机节点处理人加入变量
                addVariable(execution.getProcessInstanceId(), randomTaskAssignee, kaiteRandomUserTask.getId(), randomTaskEntity.getUserId(), execution);

                return randomTaskEntity.getUserId();
            } else {
                throw new ActivitiException("获取人员信息异常!");
            }
        } else {
            List<RandomTaskEntity> randomTaskList = new ArrayList<>();
            for (int i = 0; i < assignerArrs.length; i++) {
                String id = assignerArrs[i];
                RandomTaskEntityImpl randomTaskEntity = new RandomTaskEntityImpl();
                randomTaskEntity.setTaskId(taskId);
                randomTaskEntity.setUserId(id);
                String time = simpleDateFormat.format(new Date());
                randomTaskEntity.setTime(time);
                randomTaskEntity.setCountNum("0");
                randomTaskEntity.setProcessInstanceId(execution.getProcessInstanceId());
                randomTaskEntity.setProcessDefinitionId(execution.getProcessDefinitionId());
                randomTaskEntity.setFlowElementId(kaiteRandomUserTask.getId());
                randomTaskEntity.setProcessKey(processKey);
                commandContext.getProcessEngineConfiguration().getRandomTaskDataManager().insert(randomTaskEntity);
                randomTaskList.add(randomTaskEntity);
            }
            Random random = new Random();
            int n = random.nextInt(randomTaskList.size());
            RandomTaskEntity randomTaskEntity = randomTaskList.get(n);
            //修改执行人执行次数
            randomTaskEntity.setCountNum(String.valueOf(Long.parseLong(randomTaskEntity.getCountNum()) + 1));
            String time = simpleDateFormat.format(new Date());
            randomTaskEntity.setTime(time);
            commandContext.getProcessEngineConfiguration().getRandomTaskDataManager().update(randomTaskEntity);

            //随机节点处理人加入变量
            addVariable(execution.getProcessInstanceId(), randomTaskAssignee, kaiteRandomUserTask.getId(), randomTaskEntity.getUserId(), execution);

            return randomTaskEntity.getUserId();
        }

    }

    private void addVariable(String piId, Object randomTaskAssignee, String taskId, String userId, DelegateExecution execution) {
        JSONArray jsonArray = new JSONArray();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put(piId + "-" + taskId, userId);
        jsonArray.add(jsonObject);
        ExecutionEntity executionEntity = (ExecutionEntity) execution;
        if (randomTaskAssignee != null) {
            JSONArray taskAssignee = JSONArray.parseArray(randomTaskAssignee.toString());
            taskAssignee.add(jsonObject);
        } else {
            executionEntity.getProcessInstance().setVariable(RANDOM_TASK_ASSIGNEE, jsonArray.toString());
        }
    }

    private Map<String, String> getAssigner(String piId, String taskDefinitionKey) {
        Map<String, String> assigners = new HashMap<>();
        if (Strings.isNullOrEmpty(taskDefinitionKey)) {
            return assigners;
        }
        CommandContext commandContext = Context.getCommandContext();
        String assignerNodeId = "";
        List<HistoricActivityInstance> historicActivityInstanceList = commandContext.getProcessEngineConfiguration()
                .getHistoryService().createHistoricActivityInstanceQuery().processInstanceId(piId)
                .orderByHistoricActivityInstanceStartTime().desc().list();
//        String firstNodeId = "";
        for (HistoricActivityInstance historicActivityInstance : historicActivityInstanceList) {
            if (historicActivityInstance.getActivityId().equals(taskDefinitionKey) || historicActivityInstance.getAssignee() == null ||
                    historicActivityInstance.getAssignee().equals("")) {
                continue;
            }
//            //第一个节点跳过
//            if (firstNodeId.equals("")) {
//                firstNodeId = historicActivityInstance.getActivityId();
//                continue;
//            }
//            if (firstNodeId.equals(historicActivityInstance.getActivityId())) {
//                continue;
//            }
            if (Strings.isNullOrEmpty(assignerNodeId)) {
                assignerNodeId = historicActivityInstance.getActivityId();
                assigners.put("assigner", historicActivityInstance.getAssignee());
                continue;
            }
            if (assignerNodeId.equals(historicActivityInstance.getActivityId())) {
                continue;
            }
            assigners.put("frontAssigner", historicActivityInstance.getAssignee());
            break;
        }
        return assigners;
    }

    /**
     * Default way of leaving a BPMN 2.0 activity: evaluate the conditions on the outgoing sequence flow and take those that evaluate to true.
     * BPMN 2.0活动的默认处理：计算输出序列的条件并从计算为真条件流出
     */
    public void leave(DelegateExecution execution) {
        bpmnActivityBehavior.performDefaultOutgoingBehavior((ExecutionEntity) execution);
    }

    public void leaveIgnoreConditions(DelegateExecution execution) {
        bpmnActivityBehavior.performIgnoreConditionsOutgoingBehavior((ExecutionEntity) execution);
    }

    @Override
    public void trigger(DelegateExecution execution, String signalName, Object signalData) {
        // concrete activity behaviours that do accept signals should override this method;
        // 实现活动行为具有接收信号的功能应该复写此方法
        throw new ActivitiException("this activity isn't waiting for a trigger");
    }

    protected String parseActivityType(FlowNode flowNode) {
        String elementType = flowNode.getClass().getSimpleName();
        elementType = elementType.substring(0, 1).toLowerCase() + elementType.substring(1);
        return elementType;
    }

    public void setVariablesCalculator(VariablesCalculator variablesCalculator) {
        bpmnActivityBehavior.setVariablesCalculator(variablesCalculator);
    }

    public void validatePersonnelClearance(List<String> userIds, BpmnModel bpmnModel, String nodeCode) {
        // 校验逻辑
        FlowElement flowElement = bpmnModel.getMainProcess().getFlowElement(nodeCode);
        validatePersonnelClearance(userIds, flowElement);
    }

    public void validatePersonnelClearance(List<String> userIds, FlowElement flowElement) {
        if (flowElement instanceof KaiteBaseUserTask) {
            KaiteBaseUserTask kaiteBaseUserTask = (KaiteBaseUserTask) flowElement;
            boolean securityEnable = kaiteBaseUserTask.getTaskBasicConfig().getSecurityEnable();
            String securityCode = kaiteBaseUserTask.getTaskBasicConfig().getSecurityCode();
            if (securityEnable && !Strings.isNullOrEmpty(securityCode)) {
                Context.getCommandContext().getProcessEngineConfiguration().getUserNodeValidator().
                        validatePersonnelClearance(userIds, flowElement.getName(), securityCode);
            }
        }
    }

    public void validatePersonnelClearance(String userId, BpmnModel bpmnModel, String nodeCode) {
        List<String> userIds = new ArrayList<>();
        userIds.add(userId);
        validatePersonnelClearance(userIds, bpmnModel, nodeCode);
    }

    /**
     * 添加预警信息
     */
    public void addWarning(TaskEntity task, BpmnModel bpmnModel) {
        FlowElement flowElement = bpmnModel.getMainProcess().getFlowElement(task.getTaskDefinitionKey());
        if (flowElement instanceof KaiteBaseUserTask) {
            KaiteBaseUserTask kaiteBaseUserTask = (KaiteBaseUserTask) flowElement;
            TaskEarlyWarningAndPostponementConfigImpl warningConfig =
                    kaiteBaseUserTask.getTaskEarlyWarningAndPostponementConfig();
            if (!warningConfig.isEnabled()) {
                return;
            }
            //处理时限
            Date processingTime = calculateTime(new Date(), Integer.valueOf(warningConfig.getProcessingTimeLimitDuration()),
                    warningConfig.getProcessingTimeLimitUnitCode(), true);
            //下次提醒时间
            Date nextTime = calculateTime(processingTime, Integer.valueOf(warningConfig.getWarningTimeLimitDuration()),
                    warningConfig.getWarningTimeLimitUnitCode(), false);
            insertWarningLog(task, processingTime, nextTime);
        }


    }

    public static Date calculateTime(Date date, int amount, String timeUnit, boolean isAdd) {
        Instant instant = date.toInstant();
        ZoneId zone = ZoneId.systemDefault();
        LocalDateTime currentTime = instant.atZone(zone).toLocalDateTime();
        // 根据给定的时间类型设置增量
        ChronoUnit unit;
        switch (timeUnit) {
            case "DAY":
                unit = ChronoUnit.DAYS;
                break;
            case "HOUR":
                unit = ChronoUnit.HOURS;
                break;
            case "MINUTE":
                unit = ChronoUnit.MINUTES;
                break;
            case "SECOND":
            case "MILLISECOND":
                unit = ChronoUnit.SECONDS;
                break;
            default:
                throw new IllegalArgumentException("不支持的时间类型: " + timeUnit);
        }

        // 计算并返回结果
        LocalDateTime currentTime2;
        if (isAdd) {
            currentTime2 = currentTime.plus(amount, unit);
        } else {
            currentTime2 = currentTime.minus(amount, unit);
        }
        return convertToDate(currentTime2);
    }

    public static Date convertToDate(LocalDateTime localDateTime) {
        return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
    }

    private void insertWarningLog(TaskEntity task, Date processingTime, Date nextTime) {
        EarlyWarningEntityImpl earlyWarningEntity = new EarlyWarningEntityImpl();
        earlyWarningEntity.setCreateTime(new Date());
        earlyWarningEntity.setTaskId(task.getId());
        earlyWarningEntity.setTaskName(task.getName());
        earlyWarningEntity.setProcessInstanceId(task.getProcessInstanceId());
        earlyWarningEntity.setProcessDefinitionId(task.getProcessDefinitionId());
        earlyWarningEntity.setProcessingTime(processingTime);
        earlyWarningEntity.setNextTime(nextTime);
        earlyWarningEntity.setCreateTime(new Date());
        earlyWarningEntity.setState("1");
        Context.getCommandContext().getProcessEngineConfiguration().getEarlyWarningDataManager().insert(earlyWarningEntity);
    }


}
